fix(cli): inline label: is the default-locale source for i18n coverage (#3103)#3117
Merged
Conversation
…age (#3103) A fresh `npm create objectstack` scaffold linted with 4 user-side i18n errors out of the box, for its own `<ns>_note` object — every one of them for a string the template *does* author inline (`label: 'Note'`, `pluralLabel: 'Notes'`, `label: 'Title'`, `label: 'Body'`). They were false positives. The inline `label:` is the default-locale source text, and the framework already says so everywhere except here: - the runtime resolver falls back to it when a bundle carries no entry (`i18n-resolver` `translateObject`: `lookupObjectField(...) ?? doc.label`), and its docstring documents the order `locale → fallbackChain → literal label from the metadata`; - `os i18n extract` *generates* bundles from it (`obj.label ?? objectName`). So coverage was demanding an `en` bundle that mechanically restates data the linter already holds, for text the runtime renders correctly. Worse, an object with no `pluralLabel` was told its `pluralLabel` was "missing translation" — a demand for a translation of a string that does not exist, unfixable by authoring anything but a bundle entry. Honour the contract instead: an inline label satisfies the default locale, and a bundle is what *other* locales need. A key with no source string anywhere is no longer an i18n gap — a missing label is `required/label`'s finding, and this file already only expected `confirmText`/`successMessage`/`description` when authored; `label`/`pluralLabel` were the outliers. Platform form fields keep their key: they omit `label` by design and let the renderer humanize the path ("name" → "Name"), so that derived text is a real source string other locales still owe a translation for. Mirrors the extractor's seed value, which the walker's docstring already promises. Verified on the issue's repro and against a real multi-locale project — the non-default-locale signal is preserved exactly: examples/app-todo before after os lint 39 errors, 79 warn 0 errors, 79 warn os i18n check 856 err, 1712 warn 0 err, 1712 warn Warnings are byte-identical; only the default-locale duplicates are gone. The platform baseline drops 2451 → 1634 hidden issues, a delta of exactly 817 — its `en` third (817×3 → 817×2) — because the platform ships English labels inline too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 17 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3103.
Takes option 2 from the issue — and it turned out to be more than "the better long-term semantics": the old rule was a false positive, contradicting a contract the rest of the framework already documents.
Why option 2, not option 1
A fresh scaffold's 4 errors are all for strings the template does author inline:
The inline
label:is the default-locale text, and the framework says so everywhere except in the coverage rule:i18n-resolver.tstranslateObject:lookupObjectField(bundle, objectName, 'label', opts) ?? doc.label. Its docstring states the order: requested locale → fallbackChain → literallabelfrom the metadata. Nothing renders broken or empty.os i18n extractgenerates bundles from it —pushEntry(..., obj.label ?? objectName, ...).So coverage was demanding an
enbundle that mechanically restates data the linter already holds, for text that renders correctly. Option 1 would have shipped that duplicate into the template and frozen the ritual for every user: the errors return the moment someone adds their own object.It's also not only about the template. An object with a
labelbut nopluralLabelwas told itspluralLabelwas "missing translation" — a demand to translate a string that doesn't exist, unfixable by authoring anything except a bundle entry.The change
An inline label satisfies the default locale; a bundle is what other locales need. A key with no source string anywhere is no longer an i18n gap — a missing label is already
required/label's finding (lint.ts:155-225).This also removes an inconsistency rather than adding a special case:
collectExpectedKeysalready only expectedconfirmText/successMessage/descriptionwhen authored.label/pluralLabelwere the outliers.Platform form fields keep their key: they omit
labelby design and let the renderer humanize the path (name→Name), so that derived text is a real source string other locales still owe a translation for. This mirrors the extractor's seed value — which the coverage walker's docstring already promises it does. (My own new test caught that I'd otherwise have silently dropped ~800 platform keys from non-default-locale coverage.)Verification
The issue's repro, end to end —
create-objectstack→os lint:And against a real multi-locale project, the non-default-locale signal is preserved exactly:
examples/app-todoos lintos i18n checkWarnings are byte-identical — no real gap was dropped; only the default-locale duplicates are gone.
os i18n checknow reports the truth: "Non-default locales have gaps but the default locale is fully covered."The platform baseline drops 2451 → 1634 hidden issues — a delta of exactly 817, its
enthird (817×3 locales → 817×2) — because the platform ships English labels inline too. That arithmetic is what confirms only theenshare vanished.Full CLI suite green: 548 tests / 52 files. Two existing tests asserted the old behaviour and were rewritten to test the real intent (a genuine default-locale gap = a string in another locale with no source text for the default one), plus 7 new tests pinning the scaffold shape, the zh-CN-first case, and the bundle-only case.
Follow-ups (not in scope)
os lintignores a project's declaredi18n.defaultLocaleand always lints against'en'(the oclif flag'sdefault: 'en'makes "declared" indistinguishable from "defaulted"). This change reduces the blast radius but doesn't fix it.options: [{value, label}]entirely; the extractor handles both.AGENTS.mddocuments ansrc/i18n/directory it never creates, while real examples usesrc/translations/.🤖 Generated with Claude Code